local Utility = {
}

Utility.suitsLevel = { Card.Diamond, Card.Club, Card.Heart, Card.Spade, Card.NoSuit }
Utility.numberLevel = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1, 2, 14, 15}

---返回花色level索引
---@param suit integer
---@return integer
Utility.checkSuit = function (suit, special_rule)
  special_rule = special_rule or Utility.suitsLevel
  return table.indexOf(special_rule, suit)
end

---返回点数level索引
---@param number integer
---@return integer
Utility.checkNumber = function (number, special_rule)
  special_rule = special_rule or Utility.numberLevel
  return table.indexOf(special_rule, number)
end

---根据五十K规则判断是否为下一个点数
---@param num integer
---@return integer
Utility.getNextNumber = function (num)
  if num < 13 then
    return num + 1
  elseif num == 13 then
    return 1
  end
  return 0
end

---检查是否构成Flush
---@param cards integer[]
---@param min integer? @最少要多少个
---@param extend_to integer? @特殊规则，是否将A和2算入末尾
---@param include_one boolean? @特殊规则，在extend_to大于0时是否计入12345
---@return integer @返回最后一个数字，返回0则不构成Flush
Utility.isFlush = function(cards, min, extend_to, include_one)
  if not table.contains({ 1, 2 }, extend_to) then extend_to = 0 end
  include_one = include_one or false
  cards = table.simpleClone(cards)
  local seen = {}
  for _, c in ipairs(cards) do
    if seen[c] then
      return 0 -- 有重复
    end
    seen[c] = true
  end
  table.sort(cards)
  --排除不合理情况
  if cards[#cards] > 13 then return 0 end
  if #cards < (min or 0) then return 0 end
  if extend_to > 0 and (not include_one) and cards[1] <= extend_to then return 0 end
  -- 检查普通连续情况
  local normal = true
  for i = 2, #cards do
    if cards[i] ~= cards[i-1] + 1 then
      normal = false
      break
    end
  end
  if normal then
    if #cards == 13 then return 2 end
    return cards[#cards] -- 最后一张牌
  end
  -- 检查跨越 13-1 的情况
  -- 先把 1 当作 14，再判断
  local wrap_cards = {}
  for _, c in ipairs(cards) do
    if c == 1 and extend_to > 0 then
      table.insert(wrap_cards, 14) -- 把 A(1) 移到末尾当 14
    elseif c == 2 and extend_to > 1 then --同理
      table.insert(wrap_cards, 15)
    else
      table.insert(wrap_cards, c)
    end
  end
  table.sort(wrap_cards)
  local wrap = true
  for i = 2, #wrap_cards do
    if wrap_cards[i] ~= wrap_cards[i-1] + 1 then
      wrap = false
      break
    end
  end
  if wrap then
    local last = wrap_cards[#wrap_cards]
    if last > 13 then last = last - 13 end -- 把虚拟的 14/15 还原成 1/2
    return last
  end
  return 0
end

---判断是否是连对
---@param number_list integer[]
---@param min integer? @最少要多少对
---@param extend_to integer? @特殊规则，是否将A和2算入末尾
---@param include_one boolean? @特殊规则，在extend_to大于0时是否计入12345
---@return integer @返回最后一个数字，返回0则不构成Wings
Utility.isWings = function(number_list, min, extend_to, include_one)
  number_list = table.simpleClone(number_list)
  table.sort(number_list)
  if #number_list % 2 ~= 0 then return 0 end
  local even_list = {}
  if table.every(number_list, function (n, index)
    if index % 2 ~= 0 then return true
    else
      table.insert(even_list, n)
      return n == number_list[index - 1]
    end
  end) then
    return Utility.isFlush(even_list, min, extend_to, include_one)
  end
  return 0
end

---判断炸弹（即点数是否一样）
---@param number_list integer[]
---@return integer @返回点数，不为炸弹则返回0
Utility.isBoom = function(number_list)
  if table.every(number_list, function (n)
    return n == number_list[1]
  end) then
    return number_list[1]
  end
  return 0
end

---比较单牌
---@param card1 Card|integer @牌1
---@param card2 Card|integer  @牌2
---@param consider_suit boolean? @是否考虑引入花色比较
---@param special_level table? @特殊花色大小规则（小的在前）
---@return integer @胜出卡牌的id
Utility.compareSingleCard = function(card1, card2, consider_suit, special_level)
  special_level = special_level or Utility.suitsLevel
  if type(card1) == "number" then
    card1 = Fk:getCardById(card1)
  end
  if type(card2) == "number" then
    card1 = Fk:getCardById(card2)
  end
  if card1.number == card2.number then
    if consider_suit then
      if Utility.checkSuit(card1.suit) == Utility.checkSuit(card2.suit) then
      return 0
      else
        return Utility.checkSuit(card1.suit) > Utility.checkSuit(card2.suit) and card1.id or card2.id
      end
    else
      return 0
    end
  else
    return Utility.checkNumber(card1.number) > Utility.checkNumber(card2.number) and card1.id or card2.id
  end
end

---比较杂色顺子的大小
---@param cards1 integer[]
---@param cards2 integer[]
---@param consider_lenth boolean?
---@return integer[]
Utility.compareFlush = function (cards1, cards2, consider_lenth)
  if #cards1 < 5 or #cards2 < 5 then return {} end
  if #cards1 ~= #cards2 then
    if not consider_lenth then
      return {}
    else
      return #cards1 > #cards2 and cards1 or cards2
    end
  else
    local num_map1, num_map2 = table.map(cards1, function (id)
      return Fk:getCardById(id).number
    end), table.map(cards2, function (id)
      return Fk:getCardById(id).number
    end)
    table.sort(num_map1, function (a, b)
      return a < b
    end)
    table.sort(num_map2, function (a, b)
      return a < b
    end)
    if num_map1[#num_map1] == num_map2[#num_map2] then return {} end
    return num_map1[#num_map1] > num_map2[#num_map2] and cards1 or cards2
  end
end

---判断牌型(五十K规则)
---@param cards integer[]
---@return string @牌型
Utility.judgeCardsType = function (cards)
  local num = #cards --长度
  if num == 0 then
    return ""
  end

  --单牌
  if num == 1 then
    return "single__" .. Utility.checkNumber(Fk:getCardById(cards[1]).number) .. "__" .. Utility.checkSuit(Fk:getCardById(cards[1]).suit)
  end

  local number_list = table.map(cards, function (id) --点数数列
    return Fk:getCardById(id).number or 0
  end)
  --对子
  if num == 2 then
    return number_list[1] == number_list[2] and "pair__" .. Utility.checkNumber(number_list[1]) or ""

  --五十K/三张
  elseif num == 3 then
    if number_list[1] == number_list[2] and number_list[1] == number_list[3] then
      return "triple__" .. number_list[1]

    elseif table.every(number_list, function (n)
      return table.contains({ 5, 10, 13 }, n)
    end) then
      if table.every(cards, function (id)
        return Fk:getCardById(id).suit == Fk:getCardById(cards[1]).suit
      end) then
        return "ftkPure__" .. Utility.checkSuit(Fk:getCardById(cards[1]).suit)
      end

      return "ftkFancy__"
    end

  else
    table.sort(number_list, function (a, b) --升序排列
      return a < b
    end)

    --炸弹
    if table.every(number_list, function (n)
      return n == number_list[1]
    end) then
      return "boom__" .. num .. "__" .. Utility.checkNumber(number_list[1])
    end
    
    --三带二
    if #number_list == 5 then
      local number_map = {}
      for _, n in ipairs(number_list) do
        if number_map[n] then
          number_map[n] = number_map[n] + 1
        else
          number_map[n] = 1
        end
      end
      for k, v in pairs(number_map) do
        if v > 2 then
          return "triwith2__" .. Utility.checkNumber(k)
        end
      end
    end

    --飞机
    if #number_list % 5 == 0 then
      local groups = #number_list // 5
      local number_map = {}
      for _, n in ipairs(number_list) do
        if number_map[n] then
          number_map[n] = number_map[n] + 1
        else
          number_map[n] = 1
        end
      end
      local _number_map = {}
      for k, v in pairs(number_map) do
        if v > 2 then
          table.insert(_number_map, k)
        end
      end
      if #_number_map > 1 then
        for _, i in ipairs(_number_map) do
          local tempi = i
          local suc = true
          local _list = { i }
          for m = 1, groups - 1 do
            if table.find(_number_map, function(s)
              return Utility.getNextNumber(tempi) == s
            end) then
              tempi = Utility.getNextNumber(tempi)
              table.insert(_list, tempi)
            else
              suc = false
              break
            end
          end
          if suc and not (table.contains(_list, 3) and table.contains(_list, 13)) then
            return "plane__" .. groups .. "__" .. Utility.checkNumber(_list[#_list])
          end
        end
      end
    end

    --天炸
    if number_list == { 14, 14, 15, 15 } then return "superBoom" end

    if number_list[#number_list] > 13 then return "" end
    --连对
    local last_num = Utility.isWings(number_list, 2, 2, true)
    if last_num ~= 0 then
      return "wings__" .. #number_list // 2 .. "__" .. Utility.checkNumber(last_num)
    end
    
    --顺子
    if num > 4 then
      local end_number = Utility.isFlush(number_list, 5, 2, true) --顺子的末尾点数
      if end_number ~= 0 then
        if table.every(cards, function (id)
          return Fk:getCardById(id).suit == Fk:getCardById(cards[1]).suit
        end) then
          --同花顺
          return "flushPure__" .. num .. "__" .. Utility.checkNumber(end_number) .. "__" .. Utility.checkSuit(Fk:getCardById(cards[1]).suit)
        else
          return "flush__" .. num .. "__" .. Utility.checkNumber(end_number)
        end
      end
    end
  end

  return ""
end

---@param type_pattern string
---@return table
Utility.splitByString = function(type_pattern, sep)
  sep = sep or "__"
  local result = {}
  local i = 1
  for c in (type_pattern..sep):gmatch("(.-)"..sep) do
      result[i] = c
      i = i + 1
  end
  return result
end

---检查是否能响应（五十K规则）
---@param from_cards integer[] @主动响应的牌
---@param to_cards integer[] @等待被响应的牌
---@return table @返回两手牌的牌型和能否响应
Utility.canResponse = function (from_cards, to_cards)
  local handler = { "ftkFancy", "ftkPure", "boom", "flushPure", "superBoom" }
  local from_type, to_type = Utility.judgeCardsType(from_cards), Utility.judgeCardsType(to_cards)
  local is_valid = false
  if from_type ~= "" and to_type ~= "" then
    local clip1, clip2 = Utility.splitByString(from_type), Utility.splitByString(to_type)
    for i = 2, 3 do
      if clip1[i] then
        clip1[i] = tonumber(clip1[i])
      end
      if clip2[i] then
        clip2[i] = tonumber(clip2[i])
      end
    end
    if table.contains(handler, clip1[1]) then
      if table.contains(handler, clip2[1]) then --套牌比较
        local index1, index2 = table.indexOf(handler, clip1[1]), table.indexOf(handler, clip2[1])
        if index1 > index2 then
          is_valid = index2 < 3 or index1 == 5
        elseif index1 == index2 and index1 == 2 then
          is_valid = clip1[2] > clip2[2]
        elseif table.contains({3, 4}, index1) and table.contains({3, 4}, index2) then
          if clip1[2] > clip2[2] then
            is_valid = true
          elseif clip1[2] == clip2[2] then
            if index1 > index2 then
              is_valid = true
            elseif index1 == index2 then
              for i = 3, #clip1 do
                if clip1[i] > clip2[i] then
                  is_valid = true
                  break
                elseif clip1[i] < clip2[i] then
                  break
                end
              end
            end
          end
        end
      else
        is_valid = true
      end
    elseif not table.contains(handler, clip2[1]) and clip1[1] == clip2[1] then --小牌比较
      for i = 2, #clip1 do
        if clip1[i] > clip2[i] then
          is_valid = true
          break
        elseif clip1[i] < clip2[i] then
          break
        end
      end
    end
  end
  return { from_type = from_type, to_type = to_type, can_response = is_valid }
end

---拿队友（五十K）
---@param room Room
---@param player ServerPlayer
---@return ServerPlayer[]
Utility.getFTKAllys = function (room, player)
  local team_record = room:getTag("FTKteams")
  for k, v in pairs(team_record) do
    if table.contains(v, player) then
      return room:getTag("FTKteams")[k]
    end
  end
  return {}
end

--桥牌函数
---根据Card.<Suit>返回具体的字符串符号
---@param suit integer
---@return string
Utility.getSuitString = function(suit)
  if suit == Card.Spade then
    return "log_spade"
  elseif suit == Card.Heart then
    return "log_heart"
  elseif suit == Card.Club then
    return "log_club"
  elseif suit == Card.Diamond then
    return "log_diamond"
  elseif suit == Card.NoSuit then
    return "NT"
  end
  return ""
end

---定约
---@param value table
---@return string
Utility.encodeContract = function(value)
  local level = value[1]
  local trump = value[2]
  local doubleType = value[3]

  local ret = tostring(level)

  ret = ret .. Fk:translate(Utility.getSuitString(trump))

  if doubleType == 1 then
    ret = ret .. "X"
  elseif doubleType == 2 then
    ret = ret .. "XX"
  end

  return ret
end

Utility.encodeResult = function(room)
  local contract = room:getBanner("@[bridge_contract]glob")
  local level = contract[1]
  local trump = contract[2]
  local doubleType = contract[3]
  local declarer = room:getLord()
  local seatTab = { "N", "W", "S", "E" }
  local requiredTricks = 6 + level
  local scorePlayer = declarer.seat % 2 == 0 and room.players[2] or room.players[1]
  local actualTricks = scorePlayer:getMark("@bridge_win_tricks")

  local ret = tostring(level)

  ret = ret .. seatTab[declarer.seat]
  ret = ret .. Fk:translate(Utility.getSuitString(trump))

  if doubleType == 1 then
    ret = ret .. "X"
  elseif doubleType == 2 then
    ret = ret .. "XX"
  end

  local x = actualTricks - requiredTricks
  if x == 0 then
    ret = ret .. "="
  elseif x > 0 then
    ret = ret .. "+" .. tostring(x)
  else
    ret = ret .. tostring(x)
  end

  return ret
end

---根据桥牌规则比较牌面
---@param card1 Card
---@param card2 Card
---@param trumpSuit integer?
---@return boolean
Utility.cardLarger = function(card1, card2, trumpSuit)
  local suit1, suit2 = card1.suit, card2.suit
  local num1, num2 = card1.number, card2.number

  -- 将牌最大
  if suit1 == trumpSuit and suit2 ~= trumpSuit then return true end
  if suit2 == trumpSuit and suit1 ~= trumpSuit then return false end

  -- 同花色比较点数
  if suit1 == suit2 then
    if num1 == 1 then num1 = 14 end -- A最大
    if num2 == 1 then num2 = 14 end
    return num1 > num2
  end

  -- 不同花色，引牌花色最大
  return false
end

---根据桥牌规则比较花色
---@param suit1 integer
---@param suit2 integer
---@param trumpSuit integer?
---@return boolean
Utility.suitLarger = function(suit1, suit2, trumpSuit)
  local lvl_tab = {
    [Card.Club] = 1,
    [Card.Diamond] = 2,
    [Card.Heart] = 3,
    [Card.Spade] = 4,
    [Card.NoSuit] = 5,
  }

  local lvl1, lvl2 = lvl_tab[suit1], lvl_tab[suit2]
  if suit1 == trumpSuit then lvl1 = 10 end
  if suit2 == trumpSuit then lvl2 = 10 end
  return lvl1 > lvl2
end

Utility.fillAuctionChoices = function(choices, lastLevel, lastSuit, cannotDouble, cannotRedouble)
  lastLevel = lastLevel or 1
  for i = lastLevel ,7 do
    for _, j in ipairs { Card.Club, Card.Diamond, Card.Heart, Card.Spade, Card.NoSuit } do
      if lastLevel < i or lastSuit == nil or Utility.suitLarger(j, lastSuit, Card.NoSuit) then
        table.insert(choices, tostring(i) .. "%arg:::" .. Utility.getSuitString(j))
      end
    end
  end

  if not cannotDouble then
    table.insert(choices, "bridge-auction-double")
  end

  if not cannotRedouble then
    table.insert(choices, "bridge-auction-redouble")
  end

  table.insert(choices, "bridge-auction-pass")
end

return Utility